import { format } from 'date-fns';
import { Base64 } from 'js-base64';

export default class CommonService {
  static get isDevEnv(): boolean {
    return import.meta.env.DEV;
  }

  /**
   * Checks if the input object is null or undefined.
   * @param input The input object.
   * @return The boolean result. True means the input is null or undefined. False means NOT.
   */
  static isNullOrUndefined(input: any): boolean {
    return input === null || input === undefined;
  }

  /**
   * Checks if the input string is null, undefined or empty string.
   * @param input The input string.
   * @return The boolean result. True means the input is null, undefined or empty string. False means NOT.
   */
  static isNullOrUndefinedOrEmptyString(input: any): boolean {
    return this.isNullOrUndefined(input) || input === '';
  }

  /**
   * Checks the input to see if its type is string and the value is not empty.
   * @param input The input instance will be checked.
   * @return True means the input is string type and it is not empty.
   */
  static isNotEmptyString(input: any): boolean {
    return typeof (input) === 'string' && input !== '';
  }

  /**
   * Executes the function by using its name.
   * @param scope The function scope instance.
   * @param fnName The string of the function name.
   * @param param The parameter that the function might accept.
   * @returns Any data might be returned by this function.
   */
  static executeFunctionByName(scope: any, fnName: string, param?: any): any {
    if (!this.isNullOrUndefined(scope) && !this.isNullOrUndefinedOrEmptyString(fnName)) {
      return scope[fnName](param);
    } else {
      return null;
    }
  }

  /**
   * Calculates the percentage based on the provided dividend and divisor.
   * @param dividend The number instance.
   * @param divisor The number instance.
   * @returns The rounded result.
   */
  static calculatePercentage(dividend: number, divisor: number): number {
    return Math.round(100 * dividend / divisor);
  }

  /**
   * Gets current device system date.
   * @returns The date instance.
   */
  static getCurrentDate(): Date {
    return new Date();
  }

  /**
   * Clones the input date and returns a new Date instance with the same value.
   * @param date The source Date instance.
   * @returns The new Date instance with the same value with input Date.
   */
  static cloneDate(date: Date): Date {
    return new Date(date.getTime());
  }

  /**
   * Adds specific seconds to provided Date instance and returns as a new Date instance.
   * @param date The source Date instance.
   * @param seconds The number of seconds will be added to the provided Date intance.
   * @returns The new Date instance with added seconds to the provided Date.
   */
  static addSeconds(date: Date, seconds: number): Date {
    // Clones the input Date instance.
    const newDate = this.cloneDate(date);

    // Adds the specific seconds to the input Date.
    newDate.setSeconds(newDate.getSeconds() + seconds);

    // Returns the new Date instance.
    return newDate;
  }

  /**
   * Adds specific minutes to provided Date instance and returns as a new Date instance.
   * @param date The source Date instance.
   * @param minutes The number of minutes will be added to the provided Date intance.
   * @returns The new Date instance with added seconds to the provided Date.
   */
  static addMinutes(date: Date, minutes: number): Date {
    // Clones the input Date instance.
    const newDate = this.cloneDate(date);

    // Adds the specific minutes to the input Date.
    newDate.setMinutes(newDate.getMinutes() + minutes);

    // Returns the new Date instance.
    return newDate;
  }

  /**
   * Adds specific hours to provided Date instance and returns as a new Date instance.
   * @param date The source Date instance.
   * @param hours The number of hours will be added to the provided Date intance.
   * @returns The new Date instance with added hours to the provided Date.
   */
  static addHours(date: Date, hours: number): Date {
    // Clones the input Date instance.
    const newDate = this.cloneDate(date);

    // Adds the specific hours to the input Date.
    newDate.setHours(newDate.getHours() + hours);

    // Returns the new Date instance.
    return newDate;
  }

  /**
   * Parses date string to date.
   * @param dateString  The date string.
   * @returns Date instance.
   */
  static parseDate(dateString: string): Date {
    return new Date(Date.parse(dateString));
  }

  /**
   * Gets a random number within the provided range.
   * @param min The min number of the range beginning.
   * @param max The min number of the range ending.
   * @returns The generated random number within the provided range.
   */
  static getRandomNumberWithinRange(min: number, max: number): number {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }

  /**
   * Angular does not support iteration which goes through numbers just like for loop.
   * This method creates number array for ngFor to similate this kind of action.
   * Example with ngFor: *ngFor="let item of createRangeForNgFor(5); let currentElementIndex = index"
   * @param itemCount How many times this loop would goes through.
   * @returns The array contains numbers.
   */
  static createRangeForNgFor(itemCount: number): number[] {
    const numbers: number[] = [];

    for (let i = 0; i < itemCount; i++) {
      numbers.push(i);
    }

    return numbers;
  }

  /**
   * Accepts an array which might have the duplicate items and returns an array with unique items along.
   * @param array The array which might have the duplicate items.
   * @returns An array with unique items along.
   */
  static getUniqueArray(array: any[]): any[] {
    return array.filter((value: any, index: number, self: any[]) => self.indexOf(value) === index);
  }

  /**
   * Encodes the string to base64 format.
   * @param rawString The raw string value will be converted to base64 format.
   * @returns The encoded string value.
   */
  static encodeToBase64String(rawString: string): string {
    return Base64.encode(rawString);
  }

  /**
   * Decodes the string from base64 format.
   * @param encodedString The encoded base64 format string value.
   * @returns The decoded string value.
   */
  static decodeFromBase64String(encodedString: string): string {
    return Base64.decode(encodedString);
  }

  /**
   * Delays execution for specific millionseconds.
   */
  static delay(ms: number): Promise<number> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  /**
  * Checks if the [input] is a positive number. Returns true means it is.
  *
  * The valid input examples:
  * 12
  * 3
  * +3
  *
  * The invalid examples:
  * -12
  * 3.0
  * text1
  * 1text
  */
  static checkPositiveNumber(input: string): boolean {
    return /^\+?[0-9]+$/.test(input);
  }

  /**
  * Checks if the [input] is a number. Returns true means it is.
  *
  * The valid input examples:
  * -12
  * 3
  * 0.4
  * 5.68
  * +3
  *
  * The invalid examples:
  * .12
  * 3.0a
  * text1
  * 1text
   */
  static checkNumber(input: string): boolean {
    return /^[-+]?\d+(\.{1}\d+)?$/.test(input);
  }

  /**
   * Formats input number value to string which includes thousand separator and minimum fractions.
   */
  static formatNumber(value: number, minFractionDigits: number = 2): string {
    return new Intl.NumberFormat('en-IN', { minimumFractionDigits: minFractionDigits }).format(value);
  }

  /**
   * Formats input date with provided format.
   */
  static formatDate(date: Date, dateFormat: string): string {
    return format(date, dateFormat);
  }
}